/*
 *	This file is part of Lounge-Cast.
 *
 *  Author:  	Markus Moormann
 *	E-mail:  	mmoorman@uni-osnabrueck.de
 *	Copyright:	University of Osnabrueck (Zentrum Virtuos)
 * 	Website:	www.uni-osnabrueck.de
 *	Date:		16.02.2011
 *
 *	Lounge-Cast is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *	Lounge-Cast is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Lounge-Cast.  If not, see <http://www.gnu.org/licenses/>.
 */
package backend.service;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;

import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;

import org.apache.log4j.Logger;

import com.twmacinta.util.MD5;

import backend.dbConnection.ConnectionHelper;
import backend.exception.DAOException;
import backend.helper.HelperClass;
import backend.vo.GroupVO;
import backend.vo.MediaVO;
import backend.vo.PlaylistVO;
import backend.vo.UserVO;

/**
 * class LoginService handles login stuff 
 * 
 * @author Markus Moormann
 * @version 1.0
 *
 */
public class LoginService {
	
	/**
	 * a logger to log errors, debug information, etc.
	 */
	private static Logger LOG = Logger.getLogger(MediaService.class);
	/**
	 * the connection to database
	 */
	private Connection connection;
	/**
	 * the statement so send to database
	 */
	private Statement statement;
	/**
	 * the LDAP servers, at the moment only the first one is used
	 */
    private String server[] = {"ldaps://ldap-01.uos.de:636", 
    						   "ldaps://ldap-02.uos.de:636"};
    /**
     * environmental settings for LDAP
     */
    private Hashtable<String, String> env;
    /**
     * indicates whether environment was already set
     */
    private boolean environmentSet;
	
	/**
	 * checks whether user is allowed to access and authenticates via LDAP
	 * @param userName the username
	 * @param userPassword the password
	 * @return a <code>UserVO</code> with some information about the user
	 */
	public UserVO login(String userName, String userPassword) {
		try {
			connection = ConnectionHelper.getConnection();
			statement = connection.createStatement();
			String select = "SELECT " +
								"`user_id`," +
								"`user_name`," +
								"`user`.`group_id`," +
								"`user_password`, " +
								"`is_ldap`, " +
								"`description` " +
							"FROM " +
								"`user` " +
							"INNER JOIN " +
								"`user_group` ON " +
								"`user`.`group_id` " +
							"WHERE `user_name` = '" + userName + "'";

			LOG.debug("get user information from db");
			ResultSet rs = statement.executeQuery(select);
			//user not in database?
			if (!rs.next()) { 
				return new UserVO();
			}
			//is not a ldap user check if passwort is correct
			if(!rs.getBoolean("is_ldap")) {
				LOG.debug("try to log in via DB");
				MD5 password = new MD5();
				password.Update(userPassword);
				if(password.asHex().equals(rs.getString("user_password"))){
					UserVO loggedInUser = new UserVO(rs.getInt("user_id"), rs.getString("user_name"),
							  new GroupVO(rs.getInt("user.group_id"), rs.getString("description")));
					loggedInUser.playlists = getUserPlaylists(loggedInUser);
					
					return loggedInUser;
				} else {
					return new UserVO();
				}
			}
			//user has to be authentificated via ldap
			String bindDN = getBindDN(userName);
			LOG.debug((new StringBuilder("BindDN:")).append(bindDN).toString());
			//bind to LDAP server ok?
			if (!tryBind(bindDN, userPassword)) {
				return new UserVO();
			}
			LOG.debug("Login successful");
			//if no error occurred the authentification is OK
			return new UserVO(rs.getInt("user_id"), rs.getString("user_name"),
							  new GroupVO(rs.getInt("user.group_id")));
		} catch (SQLException e) {
			LOG.error(e);
			throw new DAOException(e);
		} finally {
			ConnectionHelper.close(statement);
			ConnectionHelper.close(connection);
		}
	}
	
	/**
	 * get all playlists the user can edit
	 * 
	 * @param user the user to get the playlists for
	 * @return a List containing all playlists the given user can edit
	 */
	public List<PlaylistVO> getUserPlaylists(UserVO user) {
		List<PlaylistVO> result = new ArrayList<PlaylistVO>();
		String selectPlaylists = "SELECT " +
									"`p`.`playlist_id`," +
									"`p`.`description`," +
									"`p`.`inheritance_level`," +
									"`p`.`inheritance_from`," +
									"`m`.`media_id`," +
									"`m`.`type`," +
									"`m`.`title`," +
									"UNIX_TIMESTAMP(`m`.`start_date`) AS `start_date`," +
									"UNIX_TIMESTAMP(`m`.`valid_date`) AS `valid_date`," +
									"`m`.`newsticker`," +
									"`m`.`duration`," +
									"`m`.`reference`," +
									"UNIX_TIMESTAMP(`m`.`added`) AS `added`," +
									"`m`.`added_by`," +
									"`m`.`approved`," +
									"`i`.`position` " +
								"FROM " +
									"`media_playlist` `p` " +
								(!user.user_group.description.equals("rootEditor") ? 
										"INNER JOIN `media_playlist_user` `mpu` " : "") +
								"LEFT JOIN" +
									"`media_playlist_items` `i` " +
										"ON(`p`.`playlist_id` = `i`.`playlist_id`)" +
								"LEFT JOIN" +
									"`media` `m` " +
										"ON (`i`.`media_id` = `m`.`media_id`)" +
								(!user.user_group.description.equals("rootEditor") ? 
											"WHERE `mpu`.`user_id` = " + user.user_id : "") +
								" ORDER BY " +
									"`p`.`playlist_id` ASC," +
									"`i`.`position` ASC";
		try {
			connection = ConnectionHelper.getConnection();
			statement = connection.createStatement();
			ResultSet rs = statement.executeQuery(selectPlaylists);
			int lastPlaylistId = 0;
			List<MediaVO> media = new ArrayList<MediaVO>();
			PlaylistVO playlist = new PlaylistVO();
			int playlist_id = 0;
			while(rs.next()) {
				if(rs.getInt("playlist_id") != lastPlaylistId) {
					lastPlaylistId = rs.getInt("playlist_id");
					if(playlist.description != null)
					{
						playlist.media = media;
						result.add(playlist);
						media = new ArrayList<MediaVO>();
					}
					
					playlist = new PlaylistVO();
					playlist_id = rs.getInt("playlist_id");
					playlist.description = rs.getString("description");
					playlist.inheritance_from = new PlaylistService().getPlaylist(rs.getInt("inheritance_from"));
					playlist.inheritance_level = rs.getInt("inheritance_level");
					playlist.playlist_id = rs.getInt("playlist_id");
				}
				if(rs.getString("media_id") != null)
				{
					MediaVO tmpMedia = new MediaVO(rs.getString("media_id"), 
							   rs.getString("type"), 
							   rs.getString("title"), 
							   rs.getLong("start_date"),
							   rs.getLong("valid_date"), 
							   rs.getBoolean("newsticker"), 
							   rs.getLong("duration"), 
							   rs.getString("reference"));
					tmpMedia.playlist_id = playlist_id;
					tmpMedia.position = rs.getFloat("position");
					tmpMedia.added = rs.getLong("added");
					tmpMedia.addedBy = new UserVO(rs.getInt("added_by"), null, null);
					media.add(tmpMedia);
				}
			}
			if(playlist.description != null)
			{
				playlist.media = media;
				result.add(playlist);
			}
		} catch (SQLException e) {
			LOG.error("Mysql-Error: " + e);
			e.printStackTrace();
			return null;
		} finally {
			ConnectionHelper.close(connection);
			ConnectionHelper.close(statement);
		}
		return result;
	}
	
	/**
	 * set all environmental settings for LDAP
	 */
	private void setEnvironment()
    {
        env = new Hashtable<String, String>();
        env.put("java.naming.factory.initial", "com.sun.jndi.ldap.LdapCtxFactory");
        env.put("java.naming.factory.control", "com.sun.jndi.ldap.ControlFactory");
        env.put("java.naming.provider.url", server[0]);
        env.put("java.naming.security.protocol", "ssl");
        //Security.addProvider(new Provider());
        System.setProperty("javax.net.ssl.keyStore", HelperClass.getProperty("sslCertificate"));
        System.setProperty("javax.net.ssl.trustStore", HelperClass.getProperty("sslCertificate"));
        environmentSet = true;
    }
	
	/**
	 * search the LDAP server for die bindDN
	 * 
	 * @param uid the username to search for
	 * @return the bindDN
	 */
	private String getBindDN(String uid) {
		String bindDN = "";
		if (!environmentSet) {
			setEnvironment();
		}
		try {
			DirContext context = new InitialDirContext(env);
			SearchControls constraints = new SearchControls();
			constraints.setSearchScope(2);
			NamingEnumeration<?> results = context.search("ou=people,dc=uni-osnabrueck, dc=de", "uid=" + uid, constraints);
			if (results.hasMore()) {
				SearchResult result = (SearchResult) results.next();
				bindDN = result.getNameInNamespace();
			}
			context.close();
		} catch (NamingException e) {
			LOG.error(e);
		}
		return bindDN;
	}
	 
	
	/**
	 * try an authorized bind
	 * 
	 * @param bindDN the bindDN
	 * @param password the password
	 * @return <code>true</code> on success, <code>false</code> else
	 */
	private boolean tryBind(String bindDN, String password) {
		if (!environmentSet) {
			setEnvironment();
		}
		env.put("java.naming.security.authentication", "simple");
		env.put("java.naming.security.principal", bindDN);
		env.put("java.naming.security.credentials", password);
		try {
			DirContext ctx = new InitialDirContext(env);
			ctx.close();
		} catch (NamingException e) {
			LOG.error(e);
			return false;
		}
		return true;
	}
}
